#!/usr/bin/env python
import sys, os, os.path, re
import datetime

import logging

import urlparse, urllib

import BeautifulSoup

import douban.client

from google.appengine.ext import webapp, db
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app, login_required

from google.appengine.api import users
from google.appengine.api import urlfetch
from google.appengine.api import memcache

from template.service.douban import *

from service.apikey import *

DOUBAN_API_HOST = "http://api.douban.com"

class DoubanAccount(db.Model):
    user = db.UserProperty(auto_current_user_add=True)
    
    request_key = db.StringProperty()
    request_secret = db.StringProperty()
    
    access_key = db.StringProperty()
    access_secret = db.StringProperty()
    
    user_id = db.StringProperty()
    
    name_id = db.StringProperty()
    nickname = db.StringProperty()
    location = db.StringProperty()
    location_id = db.StringProperty()
    
    token = db.StringProperty()
    
    created = db.DateTimeProperty(auto_now_add=True)
    
    @property
    def requesting(self):
        return self.request_key and self.request_secret and not self.authenticated
    
    @property
    def authenticated(self):
        return self.access_key and self.access_secret and self.user_id
    
    def generateToken(self):
        import hashlib, binascii
        
        return hashlib.md5(self.user.email()+self.user_id+binascii.hexlify(os.urandom(8))).hexdigest()
    
    def finish(self, key, secret, user_id):
        self.access_key = key
        self.access_secret = secret
        self.user_id = user_id
        self.token = self.generateToken()
        self.put()
        
        db.delete(self.all().filter("user =", self.user) \
                            .filter("user_id =", self.user_id) \
                            .filter("__key__ !=", self.key()))
        
    @staticmethod
    def findByUser(user):
        return DoubanAccount.all().filter("user =", user).get() if user else None
        
    @staticmethod
    def findByRequestKey(user, key):
        return DoubanAccount.all().filter("user =", user).filter("request_key =", key).get() if key else None
    
    @staticmethod
    def findByToken(token):
        return DoubanAccount.all().filter("token =", token).get()
    
    @staticmethod    
    def create(user, key, secret):
        account = DoubanAccount(user=user,
                                request_key=key,
                                request_secret=secret)
        
        account.put()
        
        return account
    
class DoubanPage(webapp.RequestHandler):
    def render(self, template_file, context=None):     
        return self.response.out.write(template.render(template_file, context))
        
class MainPage(DoubanPage):
    def get(self):
        user = users.get_current_user()
        account = DoubanAccount.findByUser(user) if user else None
        
        self.render(TEMPLE_USER if user else TEMPLE_GUEST, {
            "user" : user,
            "account" : account,
            "is_admin" : users.is_current_user_admin() if user else False,
            "login_url" : users.create_login_url(self.request.url),
            "logout_url" : users.create_logout_url(self.request.url),
        })
    
class AuthPage(DoubanPage):
    @login_required
    def get(self):
        user = users.get_current_user()
        
        request_key = self.request.get('oauth_token')
        
        client = douban.client.OAuthClient(key=DOUBAN_API_KEY, secret=DOUBAN_SECRET)
        
        if request_key:                
            account = DoubanAccount.findByRequestKey(user, request_key)
            
            if account:
                key, secret, user_id = client.get_access_token(account.request_key, account.request_secret)
                
                if key and secret and user_id:
                    account.finish(key, secret, user_id)
                    
                    if client.login(account.access_key, account.access_secret):
                        url = DOUBAN_API_HOST + "/people/%40me"
                            
                        result = urlfetch.fetch(url=url,
                                                method=urlfetch.GET,
                                                headers=client.get_auth_header("GET", url))
                        
                        if result.status_code == 200:
                            feed = BeautifulSoup.BeautifulSoup(result.content)
                            
                            entry = feed.find('entry')
                            
                            account.name_id = unicode(entry.find('db:uid').string)
                            account.nickname = unicode(entry.find('title').string)
                            account.location = unicode(entry.find('db:location').string)
                            account.location_id = entry.find('db:location')["id"]
                            account.put()
                        else:
                            self.error(result.status_code)                        
                    
                    self.redirect("/douban/rss/")
            else:
                self.error(404)
        else:
            key, secret = client.get_request_token()
            
            account = DoubanAccount.create(user, key, secret)
            
            auth_url = client.get_authorization_url(account.request_key, account.request_secret, self.request.url)
            
            self.redirect(auth_url)
                
class DenyPage(DoubanPage):
    @login_required
    def get(self):
        user = users.get_current_user()        
        account = DoubanAccount.findByUser(user)
        
        if account:
            account.delete()
            
        self.redirect("/douban/")
        
class UrlsPage(DoubanPage):
    @login_required
    def get(self, template_file = TEMPLE_URLS):
        user = users.get_current_user()
        account = DoubanAccount.findByUser(user)
        
        if not account or not account.authenticated:
            self.redirect("/douban/auth/")
        else:
            return self.render(template_file, {
                "user" : user,
                "account" : account,
            })
    
class FeedPage(DoubanPage):
    FeedUrls = {
        "me" : "/people/${user_id}/miniblog",
        "friends" : "/people/${user_id}/miniblog/contacts",
        "city" : "/event/location/${location_id}",
        "mails" : "/doumail/inbox/unread"
    }
    
    RE_MINIBLOG = re.compile('http://www.douban.com/2007#miniblog.(\w+)')
    RE_EVENT = re.compile('http://www.douban.com/2007#event.(\w+)')
    
    def fetch(self, client, id, cacheable):
        data = memcache.get(id) if cacheable else None
        expired = datetime.timedelta(hours=12).seconds
        
        if not data:
            result = urlfetch.fetch(url=id,
                                    method=urlfetch.GET,
                                    headers=client.get_auth_header("GET", id))
                        
            if result.status_code == 200:
                data = result.content
            
        if data:
            memcache.add(id, data, expired)
            
        return data
            
    def info(self, client, kind, id, cacheable=True):
        data = self.fetch(client, id, cacheable)
        lines = []
        
        if data:
            entry = BeautifulSoup.BeautifulSoup(data)

            summary = entry.find('summary')
            
            if summary:
                lines.append(u'<h3>Summary</h3>')
                lines.append(summary.string)
                
            author = entry.find('db:attribute', {"name" : "author-intro"})
            
            if author:
                lines.append(u'<h3>Author</h3>')
                lines.append(author.string)
                
            tags = []
                
            for tag in entry.findAll('db:tag', {"count" : True, "name" : True}):
                count = int(tag['count'])
                name = unicode(tag['name'])
                
                tags.append(u'<a href="http://www.douban.com/%s/tag/%s">%s</a>(%d)' % \
                            (kind, urllib.quote(name.encode('utf-8')), name, count))
                
            if len(tags) > 0:
                lines.append(u'<h3>Tags</h3>')
                lines.append(u'&nbsp;'.join(tags))
        
        return lines
    
    def fill(self, client, content):
        feed = BeautifulSoup.BeautifulSoup(content)
        
        for entry in feed.findAll('entry'):
            category = entry.find('category', scheme='http://www.douban.com/2007#kind', term=True)
            
            if category:
                term = category['term']
                
                m = self.RE_MINIBLOG.match(term)
                
                if m:
                    kind = m.group(1)
                    
                    links = {}
                    
                    for link in entry.findAll('link', href=True, rel=True):
                        links[link['rel']] = link['href']
                        
                    content = entry.find('content')
                    
                    if content:
                        lines = [content.string]
                            
                        if links.has_key('image'):
                            lines.insert(0, '<img src="%s">' % links['image'])
                        if links.has_key('related'):
                            lines += self.info(client, kind, links['related'])
                        
                        content.contents[0].replaceWith(BeautifulSoup.CData("<br><br>".join(lines)))                        
                    
                    continue
                
                m = self.RE_EVENT.match(term)
                
                if m:
                    activity = m.group(1)
                    
                    logging.info("%s", activity)
                    
                    continue
            
        return feed.prettify()
    
    def get(self, token, type):
        account = DoubanAccount.findByToken(token)
        
        if not account:
            self.error(404)
        else:
            if self.FeedUrls.has_key(type):
                from string import Template
                
                url = Template(DOUBAN_API_HOST + self.FeedUrls[type]).substitute({
                    "user_id" : account.user_id,
                    "name_id" : account.name_id,
                    "nickname" : account.nickname,
                    "location" : account.location,
                    "location_id" : account.location_id,
                })
                
                client = douban.client.OAuthClient(key=DOUBAN_API_KEY, secret=DOUBAN_SECRET)
                
                if client.login(account.access_key, account.access_secret):            
                    result = urlfetch.fetch(url=url,
                                            method=urlfetch.GET,
                                            headers=client.get_auth_header("GET", url))
                    
                    if result.status_code == 200:
                        self.response.out.write(self.fill(client, result.content))
                    else:
                        self.error(result.status_code)
                        
                else:
                    self.error(500)
            else:
                self.error(500)
    
application = webapp.WSGIApplication([
    (r'^/douban/$', MainPage),
    (r'^/douban/auth/$', AuthPage),
    (r'^/douban/deny/$', DenyPage),
    (r'^/douban/rss/$', UrlsPage),
    (r'^/douban/rss/(\w+)/(\w+)/$', FeedPage),
], debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()